package form.ui
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Rectangle;
	import flash.utils.getDefinitionByName;
	
	import form.events.ListEvent;
	import form.events.ScrollBarEvent;
	import form.events.ScrollButtonEvent;
	import form.ui.component.Layout.Box;
	import form.ui.component.Layout.events.LayoutEvent;
	import form.ui.component.List.BaseListRender;
	import form.ui.component.List.IItemRender;
	import form.ui.component.List.IListBase;
	import form.ui.component.ScrollBar.ScrollBar;
	import form.ui.component.ScrollBar.ScrollButton;
	import form.ui.component.ScrollBar.ScrollDown;
	import form.ui.component.ScrollBar.ScrollUp;
	import form.ui.core.Direction;
	import form.ui.core.FMSprite;
	import form.ui.core.IClassFactory;
	import form.ui.core.IDirectionAble;
	import form.utils.EventUtils;
	import form.utils.FunctionArray;
	import form.utils.MathUtils;

	public class ListBase extends FMSprite implements IListBase, IClassFactory, IDirectionAble
	{
		protected var scrollUpButton:ScrollButton = new ScrollUp();
		protected var scrollBar:ScrollBar = new ScrollBar();
		protected var scrollDownButton:ScrollButton = new ScrollDown();
		
		protected var isInitialized:Boolean;
		protected var renderArray:Array/* of IItemrender */;
		
		private static const DEFAULT_ITEM_RENDER:String = "form.ui.component.List.BaseListRender";
		
		private var layout:Box;		
		private var defaultRender:BaseListRender;
		
		private var callLaterArray:FunctionArray = new FunctionArray();
		private var lastknownScrollIndex:Number = 0;
		
		public function ListBase()
		{
			super();
			layout = new Box();
			layout.direction = "vertical";
			layout.autoMask = false;
			addChild(layout);
			
			this.addChild(this.scrollUpButton);
			this.addChild(this.scrollDownButton);
			this.addChild(this.scrollBar);
			
			this.width = 100;
			this.height = 100;
			this.scrollRect = new Rectangle(0,0,this.width, this.height);
			
			this.registerStyle("color");
			this.registerStyle("scrollUpUpSkin");
			this.registerStyle("scrollUpOverSkin");
			this.registerStyle("scrollUpDownSkin");
			this.registerStyle("scrollUpDisableSkin");
			this.registerStyle("scrollDownUpSkin");
			this.registerStyle("scrollDownOverSkin");
			this.registerStyle("scrollDownDownSkin");
			this.registerStyle("scrollDownDisableSkin");
			this.registerStyle("handleUpSkin");
			this.registerStyle("handleOverSkin");
			this.registerStyle("handleDownSkin");
			this.registerStyle("handleDisableSkin");
			
			this.addEventListener(ScrollBarEvent.SCROLLED, onScrolled);
			this.addEventListener(ScrollButtonEvent.SCROLL_UP, onScrollUp);
			this.addEventListener(ScrollButtonEvent.SCROLL_DOWN, onScrollDown);
			this.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
			layout.addEventListener(LayoutEvent.LAYOUT_CHANGE, onLayoutChange, false, 0, true);
			
			commitItemRender();
		}
		
		//------------------------------------------------------
		//
		// Properties
		//
		//------------------------------------------------------
		
		//------------------------------------------
		//
		// dataProvider
		//
		//------------------------------------------
		
		private var _dataProvider:Array/* of Object */;
		public function get dataProvider():Array
		{
			return _dataProvider;
		}		
		public function set dataProvider(value:Array):void
		{
			if(_dataProvider != value)
			{
				_dataProvider = value;
				measure();
				commitLayout();
			}
		}
		
		//------------------------------------------
		//
		// selectedItem
		//
		//------------------------------------------
		
		private var _selectedItem:Object;		
		public function get selectedItem():Object
		{
			return _selectedItem;
		}		
		public function set selectedItem(value:Object):void
		{
			if(this.selectedItem != value)
				isInitialized ? setItemSelected(value) : callLaterArray.addFunction(setItemSelected, value);
		}
		
		public function setItemSelected(value:Object, autoScroll:Boolean = true, dispatch:Boolean = true):void
		{
			if(this.selectedItem != value)
			{
				if(selectedItem) getRenderByData(selectedItem)[1].selected = false;
				_selectedItem = value;
				
				if(autoScroll) this.scrollToItem(value);
				else
				{
					if(dataProvider) synchScrollPosition(dataProvider.indexOf(value), false);
				}
				
				if(selectedItem)
				{
					getRenderByData(selectedItem)[1].selected = true;
					if(dispatch)dispatchEvent(new ListEvent(ListEvent.LIST_CHANGE, _selectedItem));
				}
				
			}
		}
		
		//------------------------------------------
		//
		// selectedIndex
		//
		//------------------------------------------
		
		public function get selectedIndex():int
		{
			return selectedItem ? getRenderByData(selectedItem)[0] : -1;
		}		
		public function set selectedIndex(value:int):void
		{
			if(isInitialized)
			{
				if(dataProvider && value > 0)
				{
					selectedItem = dataProvider[value];
				}
			}
			else
			{
				callLaterArray.addFunction(setSelectedIndex, value);
			}
		}
		private function setSelectedIndex(value:int):void
		{
			selectedIndex = value;
		}
		
		//------------------------------------------
		//
		// selectedAble
		//
		//------------------------------------------
		
		private var _selectedAble:Boolean = true;
		public function get selectedAble():Boolean
		{
			return _selectedAble;
		}
		
		public function set selectedAble(value:Boolean):void
		{
			_selectedAble = value;
			commitSelectedAble();
		}
		
		private function commitSelectedAble():void
		{
			for each(var item:IItemRender in renderArray)
			{
				if(item is Sprite)
				{
					Sprite(item).buttonMode = _selectedAble;
					Sprite(item).mouseEnabled = _selectedAble;
				}
			}
		}
		
		//------------------------------------------
		//
		// itemRender
		//
		//------------------------------------------
		
		private var _itemRender:String;		
		public function get itemRender():String
		{
			return _itemRender;
		}		
		public function set itemRender(value:String):void
		{
			if(_itemRender != value)
			{
				_itemRender = value;
				var CLS:Class = this.getInstance(_itemRender);
				if(CLS)var item:IItemRender = new CLS() as IItemRender;
				if(item)this.computedItemRenderName = item.fullName;				
			}
			commitItemRender();
			commitLayout();
		}
		
		private function commitItemRender():void
		{
			if(!itemRender) _itemRender = DEFAULT_ITEM_RENDER;	
			measure();
		}
		
		//------------------------------------------
		//
		// direction
		//
		//------------------------------------------
		
		public function get direction():String
		{
			return layout ? layout.direction : Direction.VERTICAL;
		}		
		public function set direction(value:String):void
		{
			layout.direction = value;
			commitLayout();
			synchScroll();
			this.draw();
		}
		
		//------------------------------------------
		//
		// showScrollBar
		//
		//------------------------------------------
		
		private var _showScrollBar:Boolean = true;				
		public function set showScrollBar(value:Boolean):void
		{
			_showScrollBar = value;
			measureScollVisible();			
		}
		public function get showScrollBar():Boolean
		{
			return _showScrollBar;
		}
		
		//------------------------------------------
		//
		// showScrollButton
		//
		//------------------------------------------
		
		private var _showScrollButton:Boolean = true;		
		public function set showScrollButton(value:Boolean):void
		{
			_showScrollButton = value;
			measureScollVisible();
		}		
		public function get showScrollButton():Boolean
		{
			return _showScrollButton;
		}
		
		//------------------------------------------
		//
		// computedItemRenderName
		//
		//------------------------------------------
		
		private var _cachedItemRenderName:String;
		public function set computedItemRenderName(value:String):void
		{
			_cachedItemRenderName = value;
		}
		
		public function get computedItemRenderName():String
		{
			return _cachedItemRenderName;
		}
		
		public function get length():uint
		{
			return _dataProvider ? _dataProvider.length : 0;
		}
				
		override public function set width(value:Number):void
		{
			super.width = value;			
			synchScrollX();
		}
		
		override public function set height(value:Number):void
		{			
			layout.height = value;
			super.height = value;			
			synchScrollY();
		}
		
		//------------------------------------------------------
		//
		// List releated functions
		//
		//------------------------------------------------------
		
		private function commitLayout():void
		{
			if(!_dataProvider) return;
			
			isInitialized = false;
			clearLayout();
			
			var i:int = 0;
			renderArray = [];
			var CLS:Class = getInstance(itemRender);
			for each(var item:Object in _dataProvider)
			{
				createItem(item, CLS);
			} 
			commitSelectedAble();
			dispatchEvent(new ListEvent(ListEvent.LIST_DRAW, null));
		}
		
		private function onLayoutChange(e:LayoutEvent):void
		{
			isInitialized = true;			
			updateScrollHeight();
			if(!callLaterArray.isEmpty)callLaterArray.execute(this);
			dispatchEvent(new ListEvent(ListEvent.CREATION_COMPLETE, null));
		}
		
		private function clearLayout():void
		{
			while(layout.numChildren > 0)layout.removeChildAt(0);
		}
		
		public function addItem(value:Object):void
		{
			if(!_dataProvider)_dataProvider = [];
			if(!renderArray)renderArray = [];
			_dataProvider.push(value);
			createItem(value, getInstance(itemRender));
		}
		
		private function createItem(data:Object, renderClass:Class):void
		{						
			var render:IItemRender = new renderClass() as IItemRender
			render.data = data;
			render.owner = this;
			renderArray.push(render);
			DisplayObject(render).addEventListener(MouseEvent.CLICK, onItemCilck, false, 0, true);
			if(render is Sprite)
			{
				Sprite(render).buttonMode = selectedAble;
				Sprite(render).mouseEnabled = selectedAble;
			}	
			if(render is FMSprite)
			{
				isVertical ? FMSprite(render).percentWidth = 100 : FMSprite(render).percentHeight = 100;
			}
			layout.addChild(render as DisplayObject);
		}
		
		private function onItemCilck(e:MouseEvent):void
		{
			if(e.currentTarget is IItemRender)
			{				
				setItemSelected(IItemRender(e.currentTarget).data, false);				
			}
		}
		
		public function removeItem(value:Object):void
		{
			var index:int;
			if(_dataProvider)
			{
				index = _dataProvider.indexOf(value);
				if(index != -1)_dataProvider.splice(index, 1);
			}
			if(renderArray)
			{
				var renderArray:Array = getRenderByData(value);
				index = renderArray[0];
				var render:IItemRender = renderArray[1];
				if(index != -1)renderArray.splice(index, 1);				
				if(render)layout.removeChild(render as DisplayObject);
			}
		}
		
		protected function getRenderByData(data:Object):Array
		{
			var a:Array = [-1, null];
			if(!renderArray)return a;
			for each(var item:IItemRender in renderArray)
			{
				if(item.data == data)
				{
					a[0] = renderArray.indexOf(item);
					a[1] = item;
					return a
				} 
			}
			return a;
		}
		
		public function getItemByIndex(index:int):Object
		{
			return _dataProvider ? _dataProvider[index] : null;
		}
		
		public function scrollToIndex(index:int):void
		{
			isInitialized ? synchScrollPosition(index) : callLaterArray.addFunction(scrollToIndex, index);
		}
		
		public function scrollToItem(value:Object):void
		{
			if(isInitialized)
			{
				if(scrollToAble)
				{
					var index:int = dataProvider.indexOf(value);
					scrollToIndex(index);
				}	
			}
			else callLaterArray.addFunction(scrollToItem, value);		
		}
		
		private function measure():void
		{
			var CLS:Class = getInstance(_itemRender);
			if(!CLS || !dataProvider)return;
			var item:DisplayObject = new CLS() as DisplayObject;			
			var computedItemHeight:Number;
			var computedItemWidth:Number;
			var computedTotal:Number;
			if(isVertical)
			{		
				computedItemHeight = item.height;
				computedItemWidth = this.width;
				computedTotal = dataProvider.length * computedItemHeight;
				if(computedTotal > this.height && computedTotal > scrollBar.width)
				{
					computedItemWidth -= (scrollBar.width + 1);
				}
				
				if(layout)
				{
					layout.width = computedItemWidth;
				}
			}
			else
			{
				computedItemHeight = this.height;
				computedItemWidth = item.width;
				computedTotal = dataProvider.length * computedItemWidth;
				if(computedTotal > this.width && computedTotal > scrollBar.width)
				{
					computedItemHeight -= (scrollBar.width + 1);
				}
				if(layout)
				{
					layout.height = computedItemHeight;
				}
			}
			
		}
		
		override public function draw():void
		{
			super.draw();
			measure();
			updateScrollHeight();
			this.scrollRect = new Rectangle(0,0,this.width, this.height);
		}
		
		public function getInstance(name:String):Class
		{
			var className:Class; 
			try
			{
				className = getDefinitionByName(name) as Class;
			}
			catch(err:Error){}
			return className;
		}
		
		private function get isVertical():Boolean
		{
			return direction == Direction.VERTICAL;
		}
		
		public function eachRender(callFunction:Function, args:Array):void
		{
			if(isInitialized)
			{
				if(!renderArray)return;
				for each(var item:IItemRender in renderArray)
				{
					callFunction(item, args);
				}
			}
			else
			{
				callLaterArray.addFunction(eachRender, callFunction, args);	
			}
		}
		
		//------------------------------------------------------
		//
		// Scroll releated functions
		//
		//------------------------------------------------------
		
		private function get scrollToAble():Boolean
		{
			return dataProvider && dataProvider.length > 0 && (scrollUpButton.visible || scrollDownButton.visible || scrollBar.visible);
		}
		
		private function measureScollVisible():void
		{
			if(!layout) return;
			var showScroll:Boolean = isVertical ? layout.contentHeight > this.height : layout.contentWidth > this.width ;
			
			if(showScroll)
			{
				this.scrollUpButton.visible = showScrollButton;
				this.scrollDownButton.visible = showScrollButton;
				this.scrollBar.visible = showScrollBar;
			}
			else
			{
				this.scrollUpButton.visible = false;
				this.scrollDownButton.visible = false;
				this.scrollBar.visible = false;
			}
		}
				
		private function synchScrollX():void
		{
			if(isVertical)
			{
				this.scrollUpButton.rotation = this.scrollBar.rotation = this.scrollDownButton.rotation = 0;
				this.scrollUpButton.x = this.width - this.scrollUpButton.width;
				this.scrollBar.x = this.width - this.scrollBar.width;
				this.scrollDownButton.x = this.width - this.scrollDownButton.width;
			}
			else
			{
				this.scrollUpButton.rotation = this.scrollBar.rotation = this.scrollDownButton.rotation = -90;				
				this.scrollUpButton.x = 0;
				this.scrollBar.x = this.scrollUpButton.x + this.scrollUpButton.width;
				this.scrollDownButton.x = this.width - this.scrollDownButton.width;
			}
		}
		
		private function synchScrollY():void
		{
			
			if(isVertical)
			{
				this.scrollUpButton.y = 0;
				this.scrollBar.y = this.scrollUpButton.height;
				this.scrollBar.height = this.height - this.scrollUpButton.height - this.scrollDownButton.height;
				this.scrollDownButton.y = this.scrollUpButton.height + this.scrollBar.height;
			}
			else
			{
				this.scrollUpButton.y = this.height - this.scrollUpButton.height;
				this.scrollBar.y = this.height - this.scrollBar.width;
				this.scrollBar.height = this.width - this.scrollUpButton.width - this.scrollDownButton.width;
				this.scrollDownButton.y = this.height - this.scrollDownButton.height;
			}
		}
		
		private function synchScroll():void
		{
			synchScrollX();
			synchScrollY();
		}
		
		private function synchScrollPosition(index:int, updateLaout:Boolean = true):void
		{
			if(scrollToAble)
			{
				index = MathUtils.constrain(index, 0, dataProvider.length);
				var value:Number = index / dataProvider.length;
				var max:int;
				var position:Number;				
				if(isVertical)
				{
					max = -this.layout.contentHeight + this.height;				
					position = -(layout.contentHeight - this.height) * value;
					if(updateLaout)layout.y = position;
					
				}
				else
				{
					max = -this.layout.contentWidth + this.width;				
					position = -(layout.contentWidth - this.width) * value;
					if(updateLaout)layout.x = position;	
				}
				this.scrollBar.handleValue = position / max;
				lastknownScrollIndex = this.scrollBar.handleValue;
			}
		}
				
		public function updateScrollHeight():void
		{
			measureScollVisible();
			if(scrollBar && isVertical && height > 0 && layout.height > 0)
			{
				this.scrollBar.handleHeight = this.height / this.layout.contentHeight;
				this.scrollBar.handleValue = lastknownScrollIndex;
			}
			else if(scrollBar && !isVertical && width > 0 && layout.width > 0)
			{
				this.scrollBar.handleHeight = this.width / this.layout.contentWidth;
				this.scrollBar.handleValue = lastknownScrollIndex;
			}
		}
		
		protected function onMouseWheel(e:MouseEvent):void
		{
			scrollDown(-e.delta * ScrollButton.MOVE)
		}
		
		protected function onScrolled(e:ScrollBarEvent):void 
		{
			this.scrollTo(e.value);
			
			EventUtils.stopPropagation(e);
		}
		
		protected function onScrollUp(e:Event):void 
		{
			this.scrollUp(ScrollButton.MOVE);
		}
		
		protected function onScrollDown(e:Event):void 
		{
			this.scrollDown(ScrollButton.MOVE);
		}	
		
		protected function scrollTo(value:Number):void 
		{
			if(!scrollToAble)return;
			value = MathUtils.constrain(value, 0, 1);
			if(isVertical)
			{
				layout.y = -(layout.contentHeight - this.height) * value;
			}
			else
			{
				layout.x = -(layout.contentWidth - this.width) * value;
			}
			
			lastknownScrollIndex = this.scrollBar.handleValue;
		}
		
		protected function scrollUp(pixels:Number):void 
		{
			this.scrollDown(-pixels);
		}
		
		protected function scrollDown(pixels:Number):void 
		{
			if(!scrollToAble)return;
			var max:int;
			if(isVertical)
			{
				var y:int = this.layout.y - pixels;
			
				if (y > 0) { y = 0; }
				max = -this.layout.contentHeight + this.height;
				if (y < max) { y = max; }
				
				this.layout.y = y;
				
				this.scrollBar.handleValue = y / max;
			}
			else
			{
				var x:int = this.layout.x - pixels;
			
				if (x > 0) { x = 0; }
				max =- this.layout.contentWidth + this.height;
				if (x < max) { x = max; }
				
				this.layout.x = x;
				
				this.scrollBar.handleValue = x / max; 
			}
			lastknownScrollIndex = this.scrollBar.handleValue;
		}
		
		//------------------------------------------------------
		//
		// Style releated settings
		//
		//------------------------------------------------------
		
		override public function get paddingLeft():Number
		{
			return layout.paddingLeft;
		}
		override public function set paddingLeft(value:Number):void
		{
			layout.paddingLeft = value;
		}
		
		override public function get paddingRight():Number
		{
			return layout.paddingRight;
		}
		override public function set paddingRight(value:Number):void
		{
			layout.paddingRight = value;
		}
		
		override public function get paddingTop():Number
		{
			return layout.paddingTop;
		}
		override public function set paddingTop(value:Number):void
		{
			layout.paddingTop = value;
		}
		
		override public function get paddingBottom():Number
		{
			return layout.paddingBottom;
		}
		override public function set paddingBottom(value:Number):void
		{
			layout.paddingBottom = value;
		}
		
		//------------------------------------------------------
		//
		// Skin releated settings
		//
		//------------------------------------------------------
		
		public function set scrollUpUpSkin(value:String):void
		{
			scrollUpButton.upSkin = value;
		}
		
		public function set scrollUpOverSkin(value:String):void
		{
			scrollUpButton.overSkin = value;
		}
		
		public function set scrollUpDownSkin(value:String):void
		{
			scrollUpButton.downSkin = value;
		}
				
		public function set scrollUpDisableSkin(value:String):void
		{
			scrollUpButton.disabledSkin = value;
		}
		
		public function set scrollDownUpSkin(value:String):void
		{
			scrollDownButton.upSkin = value;
		}
		
		public function set scrollDownOverSkin(value:String):void
		{
			scrollDownButton.overSkin = value;
		}
		
		public function set scrollDownDownSkin(value:String):void
		{
			scrollDownButton.downSkin = value;
		}
		
		public function set scrollDownDisableSkin(value:String):void
		{
			scrollDownButton.disabledSkin = value;
		}
		
		public function set handleUpSkin(value:String):void
		{
			scrollBar.handleUpSkin = value;
		}
		
		public function set handleOverSkin(value:String):void
		{
			scrollBar.handleOverSkin = value;
		}
		
		public function set handleDownSkin(value:String):void
		{
			scrollBar.handleDownSkin = value;
		}
		
		public function set handleDisableSkin(value:String):void
		{
			scrollBar.handleDisableSkin = value;
		}
		
		public function set color(value:uint):void
		{
			scrollBar.fillColor = value;
		}
		
	}
}